package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.constants.RedisConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.SignMessageDTO;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;
import static com.tianji.common.constants.RedisConstants.POINT_BOARD_KEY_PREFIX;
import static com.tianji.common.utils.DateUtils.DATE_SUFFIX_FORMATTER;


@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper rabbitMqHelper;

    @Override
    public SignResultVO addSignRecords() {
        //1、签到
        //1.1获取当前用户Id
        Long userId = UserContext.getUser();
        //1.2获取当前日期
        LocalDateTime now = LocalDateTime.now();
        String dateStr = now.format(DATE_SUFFIX_FORMATTER);
        //1.3组装key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + dateStr;
        //1。4存入redis
        Boolean exists = redisTemplate.opsForValue().setBit(key, now.getDayOfMonth() - 1, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到！");
        }
        //2、计算练习签到天数
        int signDays = countSignDays(key);

        //3、计算本次签到奖励分数
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        //4、封装Vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        //5、发送消息通知积分微服务保存该次积分记录
        rabbitMqHelper.send(LEARNING_EXCHANGE, SIGN_IN, SignMessageDTO.of(userId, vo.totalPoints()));
        //6、返回Vo
        return vo;
    }


    private int countSignDays(String key) {
        //1、从redis查询对应用户的本月所有签到记录
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(LocalDateTime.now().getDayOfMonth())).valueAt(0));
        //如果结果为空则没有签到记录，直接返回0
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        int value = result.get(0).intValue();
        int count = 0;

        while ((value & 1) == 1) {
            count++;
            value >>>= 1;
        }
        return count;
    }


    /**
     * 查询签到接口
     *
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        //1.1获取当前用户Id
        Long userId = UserContext.getUser();
        //1.2获取当前日期
        LocalDateTime now = LocalDateTime.now();
        int dayOfMonth = now.getDayOfMonth();
        String dateStr = now.format(DATE_SUFFIX_FORMATTER);
        //1.3组装key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + dateStr;
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        int signResult = result.get(0).intValue();

        // 3、循环每一天的签到情况，赋值给byte数组
        Byte[] arr = new Byte[dayOfMonth];

        int pos = dayOfMonth - 1;
        while (pos >= 0) {
            arr[pos--] = (byte) (signResult & 1);
            signResult >>>= 1;
        }
        return arr;
    }
}